package com.blandware.atleap.service.core.impl;

import com.blandware.atleap.common.util.ApplicationResourceStorage;
import com.blandware.atleap.common.util.ConvertUtil;
import com.blandware.atleap.common.util.PartialCollection;
import com.blandware.atleap.common.util.QueryInfo;
import com.blandware.atleap.model.core.ApplicationResource;
import com.blandware.atleap.model.core.ContentLocale;
import com.blandware.atleap.persistence.core.ApplicationResourceDAO;
import com.blandware.atleap.persistence.core.ContentLocaleDAO;
import com.blandware.atleap.service.core.ApplicationResourceManager;
import com.blandware.atleap.service.exception.BeanNotFoundException;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

/**
 * <p>Implementation of ApplicationResourceManager interface.
 * </p>
 * <p><a href="ApplicationResourceManagerImpl.java.html"> <i>View Source </i> </a>
 * </p>
 *
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.8 $ $Date: 2006/08/03 10:07:42 $
 */
public class ApplicationResourceManagerImpl extends BaseManagerImpl implements ApplicationResourceManager {

	/**
	 * Name of resource bundle to lookup in file system
	 */
	protected String resourceBundleName;

	/**
	 * Application resource DAO
	 */
	protected ApplicationResourceDAO applicationResourceDAO;

	/**
	 * Content locale DAO
	 */
	protected ContentLocaleDAO contentLocaleDAO;

	/**
	 * Sets name of resource bundle
	 *
	 * @param resourceBundleName Name of resource bundle
	 */
	public void setResourceBundleName(String resourceBundleName) {
		this.resourceBundleName = resourceBundleName;
	}

	/**
	 * Sets DAO for operating with application resources
	 *
	 * @param applicationResourceDAO DAO for operating with application resources
	 */
	public void setApplicationResourceDAO(ApplicationResourceDAO applicationResourceDAO) {
		this.applicationResourceDAO = applicationResourceDAO;
	}

	/**
	 * Sets DAO for operating with content locales
	 *
	 * @param contentLocaleDAO DAO for operating with content locales
	 */
	public void setContentLocaleDAO(ContentLocaleDAO contentLocaleDAO) {
		this.contentLocaleDAO = contentLocaleDAO;
	}

	/**
	 * @see com.blandware.atleap.service.core.ApplicationResourceManager#loadResources(boolean)
	 */
	public ApplicationResourceStorage loadResources(boolean overrideDynamicValues) {
		ApplicationResourceStorage applicationResourceStorage = new ApplicationResourceStorage();

		// load bundles for all locales, existent in persistent storage
		List availableLocales = contentLocaleDAO.listContentLocales(null).asList();

		// fill in our application resources object
		for ( Iterator i = availableLocales.iterator(); i.hasNext(); ) {
			ContentLocale contentLocale = (ContentLocale) i.next();
			applicationResourceStorage.mergeBundle(loadBundle(contentLocale, overrideDynamicValues));
		}

		// delete unexistent keys from persistent layer
		List resources = listApplicationResources(null).asList();
		for ( Iterator i = resources.iterator(); i.hasNext(); ) {
			ApplicationResource resource = (ApplicationResource) i.next();
			ApplicationResource.ApplicationResourceID id = resource.getId();
			String key = id.getKey();
			String localeIdentifier = id.getLocale().getIdentifier();
			Locale locale = new Locale(localeIdentifier);
			if ( !applicationResourceStorage.isMessagePresent(locale, key) ) {
				applicationResourceDAO.deleteApplicationResource(resource);
			}
		}

		return applicationResourceStorage;
	}

	/**
	 * @see com.blandware.atleap.service.core.ApplicationResourceManager#loadBundle(com.blandware.atleap.model.core.ContentLocale)
	 */
	public ApplicationResourceStorage.Bundle loadBundle(ContentLocale locale) {
		return loadBundle(locale, false);
	}

	/**
	 * @see com.blandware.atleap.service.core.ApplicationResourceManager#dropBundle(String)
	 */
	public void dropBundle(String localeIdentifier) {
		Map queryParameters = new HashMap();
		queryParameters.put("localeIdentifier", localeIdentifier);
		QueryInfo queryInfo = new QueryInfo();
		queryInfo.setQueryParameters(queryParameters);
		List resources = listApplicationResources(queryInfo).asList();
		for ( Iterator i = resources.iterator(); i.hasNext(); ) {
			ApplicationResource resource = (ApplicationResource) i.next();
			applicationResourceDAO.deleteApplicationResource(resource);
		}
	}

	/**
	 * @see com.blandware.atleap.service.core.ApplicationResourceManager#retrieveApplicationResource(String, String)
	 */
	public ApplicationResource retrieveApplicationResource(String key, String localeIdentifier) {
		ContentLocale locale = contentLocaleDAO.retrieveContentLocale(localeIdentifier);
		return retrieveApplicationResource(key, locale);
	}

	/**
	 * @see com.blandware.atleap.service.core.ApplicationResourceManager#updateApplicationResource(String, String, String, Long)
	 */
	public void updateApplicationResource(String localeIdentifier, String key, String value, Long version) throws BeanNotFoundException {
		ApplicationResource resource = retrieveApplicationResource(key, localeIdentifier);
		if ( resource == null ) {
			// check locale for existense
			ContentLocale locale = contentLocaleDAO.retrieveContentLocale(localeIdentifier);
			if ( locale == null ) {
				throw new BeanNotFoundException("Locale with identifier '" + localeIdentifier + "' does not exist");
			} else {
				resource = new ApplicationResource(locale, key, null);
			}
		} else {
            applicationResourceDAO.removeFromCache(resource);
        }
		byte[] valueBytes = ConvertUtil.convertToByteArray(value);
		if ( resource.getValue() == null || !resource.getValue().equals(valueBytes) ) {
			resource.setValue(valueBytes);

            if (version != null)
                resource.setVersion(version);

            // call to persistence layer in order to update object
			applicationResourceDAO.updateApplicationResource(resource);
		}
	}

	/**
	 * @see com.blandware.atleap.service.core.ApplicationResourceManager#listApplicationResources(com.blandware.atleap.common.util.QueryInfo)
	 */
	public PartialCollection listApplicationResources(QueryInfo queryInfo) {
		return applicationResourceDAO.listApplicationResources(queryInfo);
	}

	/**
	 * Internal service method for retrieving bundle
     *
     * @param contentLocale Content locale for which to retrieve bundle
     * @param overrideDynamicValues whether to override values with values
     *                              from filesystem
     * @return Retrieved bundle
	 */
	protected ApplicationResourceStorage.Bundle loadBundle(ContentLocale contentLocale, boolean overrideDynamicValues) {
		Locale locale = new Locale(contentLocale.getIdentifier());
		ApplicationResourceStorage.Bundle bundle = new ApplicationResourceStorage.Bundle(locale);

		String bundleName = resourceBundleName.replace('.', '/') + "_" + contentLocale.getIdentifier();

		bundleName += ".properties";
		InputStream in = null;
		Properties props = new Properties();

		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		if ( classLoader == null ) {
			classLoader = getClass().getClassLoader();
		}

		in = classLoader.getResourceAsStream(bundleName);
		if ( in != null ) {
			try {
				props.load(in);
			} catch ( IOException e ) {
				if ( log.isErrorEnabled() ) {
					log.error("Failed to load resource bundle for locale '" + contentLocale.getIdentifier() + "'. Cause: ");
					// print stack trace
					StringWriter sw = new StringWriter();
					e.printStackTrace(new PrintWriter(sw));
					log.error(sw.toString());
				}
			} finally {
				try {
					in.close();
				} catch ( Exception e ) {
				}
			}
		} else {
            if (log.isWarnEnabled()) {
                log.warn("Failed to load resource bundle for locale '" + contentLocale.getIdentifier() + "'");
            }
        }

		// fill in bundle object and create values for unexistent keys
		Enumeration keys = props.propertyNames();
		while ( keys.hasMoreElements() ) {
			String key = (String) keys.nextElement();
			String value = props.getProperty(key);
			if ( value == null ) {
				value = new String();
			}
			ApplicationResource resource = retrieveApplicationResource(key, contentLocale);
			byte[] valueBytes = ConvertUtil.convertToByteArray(value);
			if ( resource == null ) {
				resource = new ApplicationResource(contentLocale, key, value);
				applicationResourceDAO.createApplicationResource(resource);
			} else if ( overrideDynamicValues && !valueBytes.equals(resource.getValue()) ) {
				resource.setValue(valueBytes);
				applicationResourceDAO.updateApplicationResource(resource);
			} else {
				value = ConvertUtil.convertToString(resource.getValue());
			}
			bundle.putMessage(key, value);
		}

		return bundle;
	}

	/**
	 * Internal service method for retrieving application resource by key and
     * content locale
     *
     * @param key Application resource key
     * @param locale The content locale for which to return application resource
     * @return Application resource
	 */
	protected ApplicationResource retrieveApplicationResource(String key, ContentLocale locale) {
		ApplicationResource.ApplicationResourceID id = new ApplicationResource.ApplicationResourceID(locale, key);
		return applicationResourceDAO.retrieveApplicationResource(id);
	}

}
